﻿using System;
using System.Collections.Generic;
using System.Linq;
using Furiant.Figment.Agents;
using Furiant.Figment.Agents.Inventory;
using Furiant.Figment.Core;

namespace Furiant.Figment.Items.Lootable
{
    public class Equippable : Loot, IEquippable
    {
        private readonly EquipSlotMapCollection _equipSlotMaps;
        private readonly List<EquipActionCriterion<IHasInventory>> _equipActionCriteria;

        /// <summary>
        /// Creates a new instance of Equippable.
        /// </summary>
        /// <param name="nameSingle">The player-readable name of a single unit of this instance.</param>
        /// <param name="nameGrouped">The player-readable name of a group of this instance.</param>
        /// <param name="description">The player-readable description of this instance.</param>
        /// <param name="unitMass">The base mass of a single unit of this instance.</param>
        /// <param name="maxUnitsPerGroup">The maximum number of units of this instance that can be treated as a single group.</param>
        /// <param name="unitCount">The number of units this instance represents when created.</param>
        /// <param name="equipSlotMaps">The EquipSlotMapCollection that represents the valid equipment slot configurations for
        /// this instance.</param>
        /// <param name="equipActionCriteria">The collection of EquipActionCriterion which must be satisfied in order for a
        /// THasInventory to equip or unequip this instance.</param>
        public Equippable(
            string nameSingle,
            string nameGrouped,
            string description,
            int unitMass,
            int maxUnitsPerGroup,
            int unitCount,
            EquipSlotMapCollection equipSlotMaps,
            IEnumerable<EquipActionCriterion<IHasInventory>> equipActionCriteria)
            : base(nameSingle, nameGrouped, description, unitMass, maxUnitsPerGroup, unitCount)
        {
            _equipSlotMaps = equipSlotMaps;
            _equipActionCriteria = equipActionCriteria.ToList();
        }

        #region Properties

        /// <summary>
        /// The EquipSlotMappingCollection that represents the valid equipment slot configurations for this instance.
        /// </summary>
        public EquipSlotMapCollection EquipSlotMaps { get { return _equipSlotMaps; } }

        /// <summary>
        /// The collection of EquipActionCriterion which defines requirements for equipping or unequipping this instance.
        /// </summary>
        public IEnumerable<EquipActionCriterion<IHasInventory>> EquipActionCriteria { get { return _equipActionCriteria; } }

        /// <summary>
        /// Returns whether the specified target satisfies all criteria for the given action.
        /// </summary>
        /// <param name="target">The target to validate against.</param>
        /// <param name="action">The action to validate.</param>
        /// <returns></returns>
        public bool EquipActionCriteriaSatisfied(IHasInventory target, EquipAction action)
        {
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }
            return 
                !EquipActionCriteria.Any() || 
                _equipActionCriteria.Where(p => p.Action == action).All(p => p.IsSatisfied(target));
        }

        /// <summary>
        /// Whether the instance is currently equipped by a THasInventory.
        /// </summary>
        /// <returns></returns>
        public bool IsEquipped { get { return CurrentInventory.ContainsValue(this); } }

        #endregion

        #region Methods

        public virtual IEquippable GetClone()
        {
            return (Equippable)MemberwiseClone();
        }

        #endregion

        #region Abstract and Virtual Methods

        public virtual void SetEquipState(bool isEquipped)
        {
            OnEquipStateChanged(new EquipStateChangedArgs(isEquipped));
        }

        #endregion

        #region Events

        public event EventHandler EquipStateChanged;

        public void OnEquipStateChanged(EquipStateChangedArgs e)
        {
            var handler = EquipStateChanged;
            if (handler != null) handler(this, e);
        }

        public class EquipStateChangedArgs : EventArgs
        {
            private readonly bool _isEquipped;

            public EquipStateChangedArgs(bool isEquipped)
            {
                _isEquipped = isEquipped;
            }

            public bool IsEquipped { get { return _isEquipped; } }
        }

        #endregion
    }
}
